<?php
/**
 * MDB2
 * @ignore
 */
require_once 'MDB2.php';

/**
 * DESCRIPTION: A MDB2 implementation of the database interface. Soon to be
 * replacing all.
 *
 * @author  Till Klampaeckel <till@php.net>
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
 */
class Database
{
    // Public
    public $DatabaseType;      // The type of database to connect to and use (currently only handles mysql)

    // Private
    private $Context;           // A reference to the context object
    private $Connection;        // A connection to the default database
    private $FarmConnection;
    static private $instance = null;

    static function getInstance()
    {
        if (self::$instance == null) {
            self::$instance = new Database();
        }

        return self::$instance;
    }

    private function __construct()
    {
        $this->Context = Context::getInstance();
    }

    private function __clone()
    {
    }

    /**
     * CloseConnection
     *
     * @access public
     * @return bool
     */
    function CloseConnection()
    {
        if ($this->Connection) {
            return $this->Connection->disconnect();
        }
        return true;
    }

    /**
     * ConnectionError
     *
     * @todo Figure out where it is used.
     */
    function ConnectionError()
    {
        // Check the connection for errors and return them
        if (MDB2::isError($this->Connection)) {
            return $this->Connection->getMessage();
        }
    }

    /**
     * Returns the affected rows if successful (kills page execution if there is an error)
     */
    function Delete($SqlBuilder, $SenderObject, $SenderMethod, $ErrorMessage, $KillOnFail = '1')
    {
        $Connection = $this->GetFarmConnection();
        $KillOnFail = ForceBool($KillOnFail, 0);
        $status     = $Connection->exec($SqlBuilder->GetDelete());

        if (MDB2::isError($status)) {
            $this->Context->ErrorManager->AddError(
                $SqlBuilder->Context,
                $SenderObject,
                $SenderMethod,
                $ErrorMessage,
                $status->getMessage() . ' ' . $db->getUserInfo(),
                $KillOnFail
            );
            return false;
        }
        return ForceInt($status, 0);
    }

    /**
     * Execute
     */
    function Execute($Sql, $SenderObject, $SenderMethod, $ErrorMessage, $KillOnFail = '1')
    {
        if (strtolower(substr($Sql, 0, 6)) == 'select') {
            $Connection = $this->GetConnection();
        } else {
            $Connection = $this->GetFarmConnection();
        }
        $KillOnFail = ForceBool($KillOnFail, 0);
        $DataSet    = $Connection->query($Sql);
        if (!$DataSet) {
            $this->Context->ErrorManager->AddError(
                    $this->Context,
                    $SenderObject,
                    $SenderMethod,
                    $ErrorMessage,
                    $DataSet->getMessage() . ' ' . $db->getUserInfo(),
                    $KillOnFail
            );
            return false;
        }
        return $DataSet;
    }

    /**
     * getDsn()
     *
     * Create a DSN string from the configuration. If $farm = true,
     * it will attempt to return a DSN for FarmConnection
     *
     * @access private
     * @param  bool $farm
     * @see    self::GetConnection()
     * @see    self::$Connection
     * @see    self::$FarmConnection
     * @return string
     * @since  0.1.0
     */
    function GetDsn($farm = false)
    {
        if ($farm === false) {
            return sprintf(
                        '%s://%s:%s@%s/%s',
                        $this->Context->Configuration['DATABASE_SERVER'],
                        $this->Context->Configuration['DATABASE_USER'],
                        $this->Context->Configuration['DATABASE_PASSWORD'],
                        $this->Context->Configuration['DATABASE_HOST'],
                        $this->Context->Configuration['DATABASE_NAME']
            );
        }
        return sprintf(
                    '%s://%s:%s@%s/%s',
                    $this->Context->Configuration['FARM_DATABASE_SERVER'],
                    $this->Context->Configuration['FARM_DATABASE_USER'],
                    $this->Context->Configuration['FARM_DATABASE_PASSWORD'],
                    $this->Context->Configuration['FARM_DATABASE_HOST'],
                    $this->Context->Configuration['FARM_DATABASE_NAME']
        );
    }

    /**
     * DoConnection
     *
     * @access private
     * @param  bool $farm
     * @return mixed
     * @since  0.1.0
     * @uses   MDB2::connect()
     * @uses   MDB2::isError()
     * @uses   MDB2::setFetchMode()
     */
    function DoConnection($farm = false)
    {
        $dsn = $this->GetDsn($farm);
        $db  = MDB2::connect($dsn);

        if (MDB2::isError($db)) {
            $this->Context->ErrorManager->AddError(
                        $this->Context,
                        $this->Name,
                        (($farm === false)?'OpenConnection':'FarmConnection'),
                        $db->getMessage() . ' ' . $db->getUserInfo()
            );
            return false;
        }
        /**
         * Default fetchmode are associative arrays.
         */
        $db->setFetchMode(MDB2_FETCHMODE_ASSOC);

        /**
         * Do not convert to lowercase.
         */
        $db->setOption('portability', MDB2_PORTABILITY_NONE);
        return $db;
    }

    /**
     * SetCharacterEncoding
     *
     * @access private
     * @since  0.1.0
     */
    function SetCharacterEncoding()
    {
        // Set the character encoding if it is defined in the configuration.
        if ($this->Context->Configuration['DATABASE_CHARACTER_ENCODING'] != '') {
            $sql = 'SET NAMES "' . $this->Context->Configuration['DATABASE_CHARACTER_ENCODING'] . '"';
            $raw = $this->Connection->query($sql);
            if (MDB2::isError($raw)) {
                $this->Context->ErrorManager->AddError(
                    $this->Context,
                    $this->Name,
                    'SetCharacterSet',
                    $raw->getMessage() . ' ' . $db->getUserInfo()
                );
            }
        }
    }

    /**
     * GetConnection
     *
     * @access private
     * @uses   self::DoConnection()
     * @uses   self::SetCharacterEncoding()
     * @since  0.1.0
     */
    function GetConnection($farm = false)
    {
        if (!$this->Connection) {
            $this->Connection = $this->DoConnection($farm);
            if ($this->Connection === false) {
                return $this->Connection;
            }
            $this->SetCharacterEncoding();
        }
        return $this->Connection;
    }

    /**
     * GetFarmConnection
     *
     * Attempts to create a database connection to a farm backend.
     *
     * @access private
     * @todo   Figure out how this works. ;-)
     * @return mixed
     */
    function GetFarmConnection()
    {
        if ($this->FarmConnection) {
            return $this->FarmConnection;
        }
        if (empty($this->Context->Configuration['FARM_DATABASE_HOST'])) {
            return $this->GetConnection();
        }
        $this->FarmConnection = $this->DoConnection(true);
        if ($this->FarmConnection === false) {
            return $this->FarmConnection;
        }
        $this->SetCharacterEndcoding();
        return $this->FarmConnection;
    }

    /**
     * GetRow
     *
     * @param  $DataSet
     * @return array
     */
    function GetRow($DataSet)
    {
        return $DataSet->fetchRow();
    }

    /**
     * Returns the inserted ID (kills page execution if there is an error)
     *
     * @access public
     * @param  SqlBuilder
     * @param  SenderObject
     * @param  string
     * @param  string
     * @param  string
     * @param  string
     * @todo   Remove LAST_INSERT_ID in favour of nextId calls
     * @return mixed
     */
    function Insert($SqlBuilder, $SenderObject, $SenderMethod, $ErrorMessage, $UseIgnore = '0', $KillOnFail = '1')
    {
        $KillOnFail = ForceBool($KillOnFail, 0);
        $Connection = $this->GetFarmConnection();
        $status = $Connection->exec($SqlBuilder->GetInsert($UseIgnore));

        if (MDB2::isError($status)) {
            $this->Context->ErrorManager->AddError(
                $SqlBuilder->Context,
                $SenderObject,
                $SenderMethod,
                $ErrorMessage,
                $status->getMessage() . ' ' . $db->getUserInfo(),
                $KillOnFail
            );
            return false;
        }
        $query = "SELECT LAST_INSERT_ID()";
        $id    = $Connection->queryOne($query);
        if (MDB2::isError($id)) {
            $this->Context->ErrorManager->AddError(
                $SqlBuilder->Context,
                $SenderObject,
                $SenderMethod,
                $ErrorMessage,
                $id->getMessage() . ' ' . $db->getUserInfo(),
                $KillOnFail
            );
            return false;
        }
        return ForceInt($id, 0);
    }

    /**
     * RewindDataSet
     *
     * @uses   self::RowCount()
     * @return void
     */
    function RewindDataSet(&$DataSet, $Position = '0')
    {
        $Position = ForceInt($Position, 0);
        if ($this->RowCount($DataSet) > 0) {
            $DataSet->seek($Position);
        }
    }

    /**
     * RowCount
     *
     * @return int
     */
    function RowCount($DataSet)
    {
        return $DataSet->numRows();
    }

    /**
     * Returns a dataset (kills page execution if there is an error)
     *
     * @access public
     * @param
     * @param
     * @param
     * @param
     * @param
     * @return mixed
     */
    function Select($SqlBuilder, $SenderObject, $SenderMethod, $ErrorMessage, $KillOnFail = '1')
    {
        $KillOnFail = ForceBool($KillOnFail, 0);
        $Connection = $this->GetConnection();
        $DataSet    = $Connection->query($SqlBuilder->GetSelect());

        if (MDB2::isError($DataSet)) {
            $this->Context->ErrorManager->AddError(
                    $SqlBuilder->Context,
                    $SenderObject,
                    $SenderMethod,
                    $ErrorMessage,
                    $DataSet->getMessage() . ' ' . $DataSet->getUserInfo(),
                    $KillOnFail
            );
            return false;
        }
        return $DataSet;
    }

    /**
     * Returns the affected rows if successful
     * (kills page execution if there is an error)
     */
    function Update($SqlBuilder, $SenderObject, $SenderMethod, $ErrorMessage, $KillOnFail = '1')
    {
        $KillOnFail = ForceBool($KillOnFail, 0);
        $Connection = $this->GetFarmConnection();

        $status = $Connection->exec($SqlBuilder->GetUpdate());
        if (MDB2::isError($status)) {
            $this->Context->ErrorManager->AddError(
                $SqlBuilder->Context,
                $SenderObject,
                $SenderMethod,
                $ErrorMessage,
                $status->getMessage() . ' ' . $db->getUserInfo(),
                $KillOnFail
            );
            return false;
        }
        return ForceInt($status, 0);
    }
}